package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
	"github.com/streadway/amqp"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"shop_srvs/order_srv/global"
	"shop_srvs/order_srv/model"
	"shop_srvs/order_srv/proto"
	"time"
)

type Order struct {
	proto.UnimplementedOrderServer
}

func (Order) OrderList(ctx context.Context, req *proto.OrderFilterRequest) (*proto.OrderListResponse, error) {

	var total int64
	var orders []model.OrderInfo
	var data []*proto.OrderInfoResponse
	global.DB.Model(&model.OrderInfo{}).Where(&model.OrderInfo{User: req.UserId}).Count(&total)
	//引用Where零值
	global.DB.Where(&model.OrderInfo{User: req.UserId}).Scopes(Paginate(int(req.Pages),
		int(req.PagePerNums))).Find(&orders)

	for _, order := range orders {
		data = append(data, &proto.OrderInfoResponse{
			Id:           order.ID,
			UserId:       order.User,
			OrderSn:      order.OrderSn,
			PayType:      order.PayType,
			Status:       order.Status,
			Post:         order.Post,
			Total:        order.OrderMount,
			Address:      order.Address,
			SignerName:   order.SignerName,
			SingerMobile: order.SingerMobile,
			AddTime:      order.CreatedAt.Format("2006-01-02 15:04:05"),
		})
	}
	return &proto.OrderListResponse{
		Total: int32(total),
		Data:  data,
	}, nil
}
func (Order) OrderDetail(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoDetailResponse, error) {
	var order model.OrderInfo
	var orderGoods []model.OrderGoods
	var rsp proto.OrderInfoDetailResponse

	//这个订单的id是否是当前用户的订单，如果在web层用户传递过来一个id的订单，web层应该先查询一下订单id是否是当前用户的
	//后台管理系统，web层只传递order的id,如果是电商系统还需要一个用户id
	//When querying with struct, GORM will only query with non-zero fields, that means if your
	//field’s value is 0, '', false or other zero values, it won’t be used to build query conditions
	if result := global.DB.Where(&model.OrderInfo{BaseModel: model.BaseModel{ID: req.Id},
		User: req.UserId}).First(&order); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}
	//订单商品详情
	if result := global.DB.Where(&model.OrderGoods{Order: order.ID}).Find(&orderGoods); result.Error != nil {
		return nil, result.Error
	}

	orderInfo := proto.OrderInfoResponse{}
	orderInfo.Id = order.ID
	orderInfo.UserId = order.User
	orderInfo.OrderSn = order.OrderSn
	orderInfo.PayType = order.PayType
	orderInfo.Status = order.Status
	orderInfo.Post = order.Post
	orderInfo.Total = order.OrderMount
	orderInfo.Address = order.Address
	orderInfo.SignerName = order.SignerName
	orderInfo.SingerMobile = order.SingerMobile
	orderInfo.AddTime = order.CreatedAt.Format("2006-01-02 13:02:03")
	rsp.OrderInfo = &orderInfo
	for _, orderGood := range orderGoods {
		rsp.Goods = append(rsp.Goods, &proto.OrderItemResponse{
			GoodsId:    orderGood.Goods,
			GoodsName:  orderGood.GoodsName,
			GoodsPrice: orderGood.GoodsPrice,
			GoodsImage: orderGood.GoodsImage,
			Nums:       orderGood.Nums,
		})
	}
	return &rsp, nil
}

func (Order) CreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	/*
		新建订单
			1.从购物车中获取到选中的商品
			2.商品的金额自己查询	-	访问商品服务（跨微服务）
			3.库存的扣减	-	访问库存服务（跨微服务）
			4.订单的基本信息表		-	订单的商品信息表
			5.从购物车中删除已购买的记录
	*/
	var shopCarts []model.ShoppingCart
	var goodsIds []int32
	goodsNumMap := make(map[int32]int32) //kv存储 购物车单商品数量
	if result := global.DB.Where(&model.ShoppingCart{User: req.Id, Checked: true}).Find(&shopCarts); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "没有选中购物车商品")
	}

	for _, shopCart := range shopCarts {
		goodsIds = append(goodsIds, shopCart.Goods)
		goodsNumMap[shopCart.Goods] = shopCart.Nums
	}

	//todo 跨商品微服务：统计金额
	rsp, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{Id: goodsIds})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "批量商品信息失败")
	}

	//计算价格
	var orderAmount float32
	var orderGoods []*model.OrderGoods //指针，方便修改orderId
	var goodsInfos []*proto.GoodsInvInfo
	for _, good := range rsp.Data {
		orderAmount += good.ShopPrice * float32(goodsNumMap[good.Id])
		orderGoods = append(orderGoods, &model.OrderGoods{
			Goods:      good.Id,
			GoodsImage: good.GoodsFrontImage,
			GoodsName:  good.Name,
			GoodsPrice: good.ShopPrice,
			Nums:       goodsNumMap[good.Id],
		})
		goodsInfos = append(goodsInfos, &proto.GoodsInvInfo{
			GoodsId: good.Id,
			Num:     goodsNumMap[good.Id],
		})
	}

	orderSn := GenerateOrderSn(req.UserId)
	//todo 跨库存微服务：库存扣减
	if _, err := global.InventoryClient.Sell(context.Background(), &proto.SellInfo{GoodsInfo: goodsInfos, OrderSn: orderSn}); err != nil {
		return nil, status.Errorf(codes.ResourceExhausted, "扣减库存失败")
	}

	tx := global.DB.Begin()
	defer tx.Rollback()
	//todo 生成订单表
	//生成单一订单总表
	order := model.OrderInfo{
		User:         req.UserId,
		OrderSn:      orderSn,
		OrderMount:   orderAmount,
		Address:      req.Address,
		SignerName:   req.SignerName,
		SingerMobile: req.SingerMobile,
		Post:         req.Post,
	}
	if result := tx.Save(&order); result.RowsAffected == 0 {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单失败")
	}

	for _, orderGood := range orderGoods {
		orderGood.Order = order.ID
	}

	//生成订单商品表,批量插入orderGoods
	jsonString, _ := json.Marshal(order)
	if result := tx.CreateInBatches(orderGoods, len(orderGoods)); result.RowsAffected == 0 {
		tx.Rollback()
		if err := global.RabbitMQ.PublishMsg(jsonString); err != nil {
			zap.S().Error("发送rabbitmq失败，err:", err.Error())
		}
		return nil, status.Errorf(codes.Internal, "批量插入订单商品失败")
	}

	//删除购物车记录
	if result := tx.Where(&model.ShoppingCart{User: req.UserId, Checked: true}).Delete(
		&model.ShoppingCart{}); result.RowsAffected == 0 {
		tx.Rollback()
		if err := global.RabbitMQ.PublishMsg(jsonString); err != nil {
			zap.S().Error("发送rabbitmq失败，err:", err.Error())
		}
		return nil, status.Errorf(codes.Internal, "删除购物车记录失败")
	}

	//发送延时消息
	if err = global.RabbitMQDead.PublishDeadMsg(jsonString); err != nil {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "发送延时消息失败")
	}

	tx.Commit()
	return &proto.OrderInfoResponse{
		Id:      order.ID,
		OrderSn: order.OrderSn,
		Total:   order.OrderMount,
	}, nil
}

type OrderListener struct {
	Code        codes.Code
	Detail      string
	OrderAmount float32
	ID          int32
	Ctx         context.Context
}

/*
 *RollbackMessageState:撤销消息，未扣减库存前和完成订单操作、扣减库存都成功之后
 *CommitMessageState:发送订单消息给rocketmq：完成订单操作但后续操作失败
 */
// ExecuteLocalTransaction:执行本地事务
func (o *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
	/*
		新建订单
			1.从购物车中获取到选中的商品
			2.商品的金额自己查询	-	访问商品服务（跨微服务）
			3.库存的扣减	-	访问库存服务（跨微服务）
			4.订单的基本信息表		-	订单的商品信息表
			5.从购物车中删除已购买的记录
	*/
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)

	var shopCarts []model.ShoppingCart
	var goodsIds []int32
	goodsNumMap := make(map[int32]int32) //kv存储 购物车单商品数量
	if result := global.DB.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).
		Find(&shopCarts); result.RowsAffected == 0 {
		o.Code = codes.InvalidArgument
		o.Detail = "没有选中结算的商品"
		return primitive.RollbackMessageState
	}

	//有选中的购物车商品
	for _, shopCart := range shopCarts {
		goodsIds = append(goodsIds, shopCart.Goods)
		goodsNumMap[shopCart.Goods] = shopCart.Nums
	}

	//todo 跨商品微服务：统计金额
	rsp, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{Id: goodsIds})
	if err != nil {
		o.Code = codes.Internal
		o.Detail = "批量商品信息失败"
		return primitive.RollbackMessageState
	}

	//计算价格
	var orderAmount float32
	var orderGoods []*model.OrderGoods //指针，方便修改orderId
	var goodsInfos []*proto.GoodsInvInfo
	for _, good := range rsp.Data {
		orderAmount += good.ShopPrice * float32(goodsNumMap[good.Id])
		orderGoods = append(orderGoods, &model.OrderGoods{
			Goods:      good.Id,
			GoodsImage: good.GoodsFrontImage,
			GoodsName:  good.Name,
			GoodsPrice: good.ShopPrice,
			Nums:       goodsNumMap[good.Id],
		})
		goodsInfos = append(goodsInfos, &proto.GoodsInvInfo{
			GoodsId: good.Id,
			Num:     goodsNumMap[good.Id],
		})
	}

	//todo 跨库存微服务：库存扣减
	if _, err := global.InventoryClient.Sell(context.Background(), &proto.SellInfo{GoodsInfo: goodsInfos,
		OrderSn: orderInfo.OrderSn}); err != nil {
		o.Code = codes.Internal
		o.Detail = "扣减库存失败"
		return primitive.RollbackMessageState
	}

	tx := global.DB.Begin()
	//todo 生成订单表
	//生成单一订单总表
	orderInfo.OrderMount = orderAmount
	if result := tx.Save(&orderInfo); result.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "创建订单失败"
		return primitive.CommitMessageState
	}

	o.OrderAmount = orderAmount
	o.ID = orderInfo.ID
	for _, orderGood := range orderGoods {
		orderGood.Order = orderInfo.ID
	}

	//生成订单商品表,批量插入orderGoods
	if result := tx.CreateInBatches(orderGoods, len(orderGoods)); result.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "创建订单失败"
		return primitive.CommitMessageState
	}

	//删除购物车记录
	if result := tx.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Delete(
		&model.ShoppingCart{}); result.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "删除购物车记录失败"
		return primitive.CommitMessageState
	}

	//订单超时归还(订单生成成功)：发送延时消息
	if err = global.Proceduer.Start(); err != nil {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "启动producer失败"
		return primitive.CommitMessageState
	}
	defer global.Proceduer.Shutdown()

	//msg = primitive.NewMessage("order_timeout", msg.Body)
	//msg.WithDelayTimeLevel(3)
	//_, err = global.Proceduer.SendSync(context.Background(), msg)
	//if err != nil {
	//	tx.Rollback()
	//	zap.S().Errorf("发送延时消息失败:%v\n", err)
	//	o.Code = codes.Internal
	//	o.Detail = "发送延时消息失败"
	//	return primitive.CommitMessageState
	//}

	tx.Commit()
	o.Code = codes.OK
	return primitive.RollbackMessageState
}

// 隔一会会回调
func (o *OrderListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)

	//检查之前的逻辑是否完成
	if result := global.DB.Where(model.OrderInfo{OrderSn: orderInfo.OrderSn}).
		First(&orderInfo); result.RowsAffected == 0 {
		//你并不能说明这里是库存已经扣减，可能在创建订单前就报错退出了
		return primitive.CommitMessageState
	}
	return primitive.RollbackMessageState //没有订单消息直接撤销
}
func (Order) CreateOrder1(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	orderListener := OrderListener{Ctx: ctx}
	orderInfo := model.OrderInfo{
		User:         req.UserId,
		OrderSn:      GenerateOrderSn(req.UserId),
		Address:      req.Address,
		SignerName:   req.SignerName,
		SingerMobile: req.SingerMobile,
		Post:         req.Post,
	}
	bytes, _ := json.Marshal(orderInfo)

	p, err := rocketmq.NewTransactionProducer(
		&orderListener,
		producer.WithNameServer([]string{"192.168.71.130:9876"}))
	if err != nil {
		zap.S().Errorf("生成producer失败:%s", err.Error())
		return nil, status.Errorf(codes.Internal, err.Error())
	}

	//不要在一个进程中使用多个producer， 但是不要随便调用shutdown因为会影响其他的producer
	_ = p.Start()
	_, err = p.SendMessageInTransaction(context.Background(),
		primitive.NewMessage("order_reback", bytes))
	if err != nil {
		zap.S().Errorf("发送失败:%s", err.Error())
		return nil, status.Errorf(codes.Internal, "发送消息失败")
	}

	if orderListener.Code != codes.OK {
		return nil, status.Error(orderListener.Code, orderListener.Detail)
	}
	return &proto.OrderInfoResponse{
		Id:      orderListener.ID,
		OrderSn: orderInfo.OrderSn,
		Total:   orderListener.OrderAmount,
	}, nil
}

func (Order) UpdateOrderStatus(ctx context.Context, req *proto.OrderStatus) (*emptypb.Empty, error) {
	//先查询，再更新 实际上有两条sql执行， select 和 update语句
	//不是通过主键查询,而是订单号
	if result := global.DB.Model(&model.OrderInfo{}).Where("order_sn = ?", req.OrderSn).
		Update("status", req.Status); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}
	return &emptypb.Empty{}, nil
}
func OrderTimeout1(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
	for i := range msgs {
		var orderInfo model.OrderInfo
		_ = json.Unmarshal(msgs[i].Body, &orderInfo)

		fmt.Println("获取到订单超时消息：", time.Now())

		//查询订单的支付状态，如果已支付什么都不做，如果未支付，归还库存
		var order model.OrderInfo
		if result := global.DB.Where(model.OrderInfo{OrderSn: orderInfo.OrderSn}).
			First(&order); result.RowsAffected == 0 { //没有订单信息
			return consumer.ConsumeSuccess, nil
		}
		if order.Status != "TRADE_SUCCESS" {
			tx := global.DB.Begin()
			//归坏库存，我们可以模拟order中发送一个消息到order_reback中去
			//修改订单的状态已支付
			order.Status = "TRADE_CLOSED"
			tx.Save(&order)

			if err := global.Proceduer.Start(); err != nil {
				tx.Rollback()
				panic("启动producer失败")
			}

			_, err := global.Proceduer.SendSync(context.Background(),
				primitive.NewMessage("order_reback", msgs[i].Body))
			if err != nil {
				tx.Rollback()
				zap.S().Error("发送失败:", err.Error())
				return consumer.ConsumeRetryLater, nil //多重试
			}
			tx.Commit()
			return consumer.ConsumeSuccess, nil
		}
	}
	return consumer.ConsumeSuccess, nil
}
func OrderTimeout(msgs <-chan amqp.Delivery) {
	for msg := range msgs {
		var orderInfo model.OrderInfo
		_ = json.Unmarshal(msg.Body, &orderInfo)

		fmt.Println("获取到订单超时消息：", time.Now())

		//查询订单的支付状态，如果已支付什么都不做，如果未支付，归还库存
		var order model.OrderInfo
		if result := global.DB.Where(model.OrderInfo{OrderSn: orderInfo.OrderSn}).
			First(&order); result.RowsAffected == 0 { //没有订单信息
			//如果为true表示确认所有未确认的消息
			//如果为false表示确认当前消息
			msg.Ack(false)
			continue
		}

		if order.Status != "TRADE_SUCCESS" {
			tx := global.DB.Begin()
			//归坏库存，我们可以模拟order中发送一个消息到order_reback中去
			//修改订单的状态已支付
			order.Status = "TRADE_CLOSED"
			tx.Save(&order)

			if err := global.RabbitMQ.PublishMsg(msg.Body); err != nil {
				zap.S().Error("发送失败:", err.Error())
				tx.Rollback()
				msg.Reject(true)
				continue
			}
			tx.Commit()
		}
		//如果为true表示确认所有未确认的消息
		//如果为false表示确认当前消息
		msg.Ack(false)
	}
}
